/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.servlet.tags.form;

import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTag;

import org.springframework.util.StringUtils;

/**
 * Convenient super class for many html tags that render content using the databinding
 * features of the {@link AbstractHtmlElementTag AbstractHtmlElementTag}. The only thing
 * sub-tags need to do is override {@link #renderDefaultContent(TagWriter)}.
 *
 * @author Rob Harrop
 * @author Juergen Hoeller
 * @since 2.0
 */
@SuppressWarnings("serial")
public abstract class AbstractHtmlElementBodyTag extends AbstractHtmlElementTag implements BodyTag {

    private BodyContent bodyContent;

    private TagWriter tagWriter;


    @Override
    protected int writeTagContent(TagWriter tagWriter) throws JspException {
        onWriteTagContent();
        this.tagWriter = tagWriter;
        if (shouldRender()) {
            exposeAttributes();
            return EVAL_BODY_BUFFERED;
        } else {
            return SKIP_BODY;
        }
    }

    /**
     * If {@link #shouldRender rendering}, flush any buffered
     * {@link BodyContent} or, if no {@link BodyContent} is supplied,
     * {@link #renderDefaultContent render the default content}.
     *
     * @return Tag#EVAL_PAGE
     */
    @Override
    public int doEndTag() throws JspException {
        if (shouldRender()) {
            if (this.bodyContent != null && StringUtils.hasText(this.bodyContent.getString())) {
                renderFromBodyContent(this.bodyContent, this.tagWriter);
            } else {
                renderDefaultContent(this.tagWriter);
            }
        }
        return EVAL_PAGE;
    }

    /**
     * Render the tag contents based on the supplied {@link BodyContent}.
     * <p>The default implementation simply {@link #flushBufferedBodyContent flushes}
     * the {@link BodyContent} directly to the output. Subclasses may choose to
     * override this to add additional content to the output.
     */
    protected void renderFromBodyContent(BodyContent bodyContent, TagWriter tagWriter) throws JspException {
        flushBufferedBodyContent(this.bodyContent);
    }

    /**
     * Clean up any attributes and stored resources.
     */
    @Override
    public void doFinally() {
        super.doFinally();
        removeAttributes();
        this.tagWriter = null;
        this.bodyContent = null;
    }


    //---------------------------------------------------------------------
    // Template methods
    //---------------------------------------------------------------------

    /**
     * Called at the start of {@link #writeTagContent} allowing subclasses to perform
     * any precondition checks or setup tasks that might be necessary.
     */
    protected void onWriteTagContent() {
    }

    /**
     * Should rendering of this tag proceed at all. Returns '{@code true}' by default
     * causing rendering to occur always, Subclasses can override this if they
     * provide conditional rendering.
     */
    protected boolean shouldRender() throws JspException {
        return true;
    }

    /**
     * Called during {@link #writeTagContent} allowing subclasses to add any attributes to the
     * {@link javax.servlet.jsp.PageContext} as needed.
     */
    protected void exposeAttributes() throws JspException {
    }

    /**
     * Called by {@link #doFinally} allowing subclasses to remove any attributes from the
     * {@link javax.servlet.jsp.PageContext} as needed.
     */
    protected void removeAttributes() {
    }

    /**
     * The user customised the output of the error messages - flush the
     * buffered content into the main {@link javax.servlet.jsp.JspWriter}.
     */
    protected void flushBufferedBodyContent(BodyContent bodyContent) throws JspException {
        try {
            bodyContent.writeOut(bodyContent.getEnclosingWriter());
        } catch (IOException ex) {
            throw new JspException("Unable to write buffered body content.", ex);
        }
    }

    protected abstract void renderDefaultContent(TagWriter tagWriter) throws JspException;


    //---------------------------------------------------------------------
    // BodyTag implementation
    //---------------------------------------------------------------------

    @Override
    public void doInitBody() throws JspException {
        // no op
    }

    @Override
    public void setBodyContent(BodyContent bodyContent) {
        this.bodyContent = bodyContent;
    }

}
